﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JXT.PrimaryKey.Batman.Collections;
using JXT.PrimaryKey.Batman.Core.Configuration;
using JXT.PrimaryKey.Batman.Domain.Specifications;
using JXT.PrimaryKey.Batman.Domain.Quotation.Models;
using JXT.PrimaryKey.Batman.Domain.Services;
using JXT.PrimaryKey.Batman.Domain.Quotation.Services;
using JXT.PrimaryKey.Batman.Domain.Quotation.Repositories;

namespace JXT.PrimaryKey.Batman.Domain.Quotation.Services.Default
{
    public class TimeSharingQuoteService : CachedServiceBase<TimeSharingQuote>, ITimeSharingQuoteService
    {
        private static readonly TimeSpan _defaultCacheTimeSpan = TimeSpan.FromDays(1);

        private ExchangeSettingConfigurationElementCollection _exchangeSetting;

        public ExchangeSettingConfigurationElementCollection ExchangeSetting
        {
            get {
                var section = SupernovaConfigurationManager.GetServiceConfigurationSection();
                if (section != null)
                    _exchangeSetting = section.ExchangeSetting;
                return _exchangeSetting;
            }
        }

        public void Append(TimeSharingQuote entity)
        {
            if (entity == null)
                throw new ArgumentNullException("entity");
            var lastKey = buildCacheKey(entity.Code, entity.MarketType);
            TimeSharingQuote last = Cache.Get(lastKey) as TimeSharingQuote;
            if (last == null || String.IsNullOrWhiteSpace(last.Code) || last.MarketType == MarketType.Unknown)
            {
                last = LoadRepository<ITimeSharingQuoteRepository>().GetSingle(entity.Code, entity.MarketType);
                if (String.IsNullOrWhiteSpace(last.Code) || last.MarketType == MarketType.Unknown)
                    last = null;
            }
            entity.UpdateTime = new DateTime(entity.UpdateTime.Year, entity.UpdateTime.Month, entity.UpdateTime.Day, entity.UpdateTime.Hour, entity.UpdateTime.Minute, 0, DateTimeKind.Local);
            var timeConfiguration = findConfiguration(entity.MarketType);
            if (!isInMarketTime(entity.UpdateTime.TimeOfDay, timeConfiguration)) //非交易时间
                return;
            if (entity.UpdateTime.TimeOfDay == timeConfiguration.OpenAM || entity.UpdateTime.TimeOfDay == timeConfiguration.OpenPM)
            {
                //开盘第一分钟
                LoadRepository<ITimeSharingQuoteRepository>().Add(entity);
                Cache.Add(lastKey, entity, _defaultCacheTimeSpan);
                return;
            }
            if (last == null)
            {
                LoadRepository<ITimeSharingQuoteRepository>().Add(entity); //最后一条记录不存在
                Cache.Add(lastKey, entity, _defaultCacheTimeSpan);
                return;
            }
            if (entity.UpdateTime.Day == last.UpdateTime.Day) //同一天的数据
            {
                fillEmpty(last, entity.UpdateTime.AddMinutes(-1).TimeOfDay, timeConfiguration); //填充空数据至上次之后第一分钟到当前时间的前一分钟
            }
            else if (entity.UpdateTime.TimeOfDay != timeConfiguration.OpenAM)
            {
                //不同日数据，即当日还无数据，且不是当日第一条数据，说明开盘后有数据缺失
                last.UpdateTime = new DateTime(entity.UpdateTime.Year,
                    entity.UpdateTime.Month,
                    entity.UpdateTime.Day,
                    timeConfiguration.OpenAM.Hours,
                    timeConfiguration.OpenAM.Minutes, 0); //将上次数据设置为开盘数据
                LoadRepository<ITimeSharingQuoteRepository>().Add(last);
                fillEmpty(last, entity.UpdateTime.AddMinutes(-1).TimeOfDay, timeConfiguration); //然后从之后开始填充数据
            }
            LoadRepository<ITimeSharingQuoteRepository>().Add(entity); //当前时间
            Cache.Add(lastKey, entity, _defaultCacheTimeSpan);
            Cache.Add(buildCacheKey(entity.Code, entity.MarketType, entity.UpdateTime), entity, TimeSpan.FromMinutes(5));
        }

        public void Delete(DateTime endTime)
        {
            LoadRepository<ITimeSharingQuoteRepository>().Delete(endTime);
        }

        public void Finish(string code, MarketType type)
        {
            var lastKey = buildCacheKey(code, type);
            TimeSharingQuote last = Cache.Get(lastKey) as TimeSharingQuote;
            if (last == null)
                last = LoadRepository<ITimeSharingQuoteRepository>().GetSingle(code, type);
            if (last == null || last.UpdateTime.Date != DateTime.Now.Date)
                return;
            var timeConfiguration = findConfiguration(type);
            if (last.UpdateTime.TimeOfDay < timeConfiguration.CloseAM)
            {
                fillEmpty(last, timeConfiguration.CloseAM, timeConfiguration);
            }
            else if (last.UpdateTime.TimeOfDay < timeConfiguration.ClosePM)
            {
                fillEmpty(last, timeConfiguration.ClosePM, timeConfiguration);
            }
        }

        /// <summary>
        /// 填充空数据
        /// </summary>
        /// <param name="last">最后一条分时数据</param>
        /// <param name="endTime">填充结束时间（包含）</param>
        /// <param name="timeConfiguration">交易所时间配置</param>
        private void fillEmpty(TimeSharingQuote last, TimeSpan endTime, ExchangeConfigurationElement timeConfiguration)
        {
            //var skip = endTime - last.UpdateTime.ToLocalTime().TimeOfDay;
            var skip = endTime - last.UpdateTime.TimeOfDay;
            if (skip.TotalMinutes > 1) //有交易时间无数据，填充
            {
                for (int i = 1; i <= skip.TotalMinutes; i++) //从上次之后第一分钟到当前时间
                {
                    var currentTime = last.UpdateTime.AddMinutes(i);
                    if (!isInMarketTime(currentTime.TimeOfDay, timeConfiguration))
                        continue; //非交易时间，跳过
                    var emptyEntity = new TimeSharingQuote()
                    {
                        AveragePrice = last.AveragePrice,
                        ChangePrice = last.ChangePrice,
                        ChangeRate = last.ChangeRate,
                        Code = last.Code,
                        HighestPrice = last.HighestPrice,
                        LastPrice = last.LastPrice,
                        LowestPrice = last.LowestPrice,
                        MarketType = last.MarketType,
                        TradingNum = last.TradingNum,
                        TradingVolume = last.TradingVolume,
                        Turnover = last.Turnover,
                        UpdateTime = currentTime
                    };
                    LoadRepository<ITimeSharingQuoteRepository>().Add(emptyEntity);
                    Cache.Add(buildCacheKey(last.Code, last.MarketType, emptyEntity.UpdateTime), emptyEntity, TimeSpan.FromMinutes(5));
                    //System.Threading.Thread.Sleep(1);
                }
            }
        }

        public TimeSharingQuote GetCacheSingle(string code, MarketType type = MarketType.Unknown, DateTime? time = null)
        {
            if (type == MarketType.Unknown)
                type = getMarketType(code);
            var key = buildCacheKey(code, type, time);
            return Cache.Get(key) as TimeSharingQuote;
        }

        public TimeSharingQuote GetSingle(string code, MarketType type = MarketType.Unknown)
        {
            if (type == MarketType.Unknown)
                type = getMarketType(code);
            string key = buildCacheKey(code, type);
            TimeSharingQuote result = Cache.Get(key) as TimeSharingQuote;
            if (result == null)
            {
                result = LoadRepository<ITimeSharingQuoteRepository>().GetSingle(code, type);
                if (result != null)
                    Cache.Add(key, result);
            }
            return result;
            //var result = LoadRepository<ITimeSharingQuoteRepository>().GetSingle(code, type);
            //return result;
        }

        public TimeSharingQuote GetLastSingle(string code, MarketType type = MarketType.Unknown)
        {
            if (type == MarketType.Unknown)
                type = getMarketType(code);
            var lastKey = buildCacheKey(code, type);
            TimeSharingQuote last = Cache.Get(lastKey) as TimeSharingQuote;
            if (last == null)
                last = LoadRepository<ITimeSharingQuoteRepository>().GetSingle(code, type);
            return last;
        }

        public IEnumerable<TimeSharingQuote> GetLastDailyList(string code, MarketType type = MarketType.Unknown)
        {
            if (type == MarketType.Unknown)
                type = getMarketType(code);
            var list = GetList(code, type, DateTime.Now.Date);
            if (list == null | list.Count() <= 0)
            {
                var last = GetSingle(code, type);
                if (last != null)
                    list = GetList(code, type, last.UpdateTime.Date);
            }
            return list;
        }

        public IEnumerable<TimeSharingQuote> GetList(string code, MarketType type = MarketType.Unknown, DateTime? startTime = null, DateTime? endTime = null)
        {
            if (type == MarketType.Unknown)
                type = getMarketType(code);
            return LoadRepository<ITimeSharingQuoteRepository>().GetList(code, type, startTime, endTime);
        }

        private string buildCacheKey(string code, MarketType type = MarketType.Unknown, DateTime? time = null)
        {
            if (time == null)
                return String.Format("{0}_{1}_{2}_last", CacheSpace, code, type);
            else
                return String.Format("{0}_{1}_{2}_{3}", CacheSpace, code, type, time.Value.ToString("yyyyMMddHHmm"));
        }

        private ExchangeConfigurationElement findConfiguration(MarketType type)
        {
            var find = ExchangeSetting.FirstOrDefault(e => e.Name.Equals(type.ToString(), StringComparison.InvariantCultureIgnoreCase));
            if (find == null)
                return new ExchangeConfigurationElement()
                {
                    CloseAM = ExchangeSetting.DefaultCloseAM,
                    ClosePM = ExchangeSetting.DefaultClosePM,
                    Name = type.ToString(),
                    OpenAM = ExchangeSetting.DefaultOpenAM,
                    OpenPM = ExchangeSetting.DefaultOpenPM
                };
            return find;
        }

        private MarketType getMarketType(string code)
        {
            var stock = LoadService<ISecuritiesService>().GetSingle(code);
            if (stock != null)
                return stock.MarketType;
            else
                return MarketType.Unknown;
        }

        private bool isInMarketTime(TimeSpan time, ExchangeConfigurationElement timeConfiguration)
        {
            if (time < timeConfiguration.OpenAM ||
                   (time > timeConfiguration.CloseAM && time < timeConfiguration.OpenPM) ||
                   time > timeConfiguration.ClosePM) //非交易时间
                return false;
            return true;
        }
    }
}
